package com.pansoft.openplanet.util;

import android.content.Context;

import com.efounder.constant.EnvironmentVariable;
import com.efounder.util.AppContext;
import com.efounder.utils.ResStringUtil;
import com.pansoft.openplanet.R;
import com.pansoft.openplanet.activity.TradeLineActivity;
import com.pansoft.openplanet.bean.TransactionBean;
import com.utilcode.util.LogUtils;
import com.utilcode.util.TimeUtils;

import org.bitcoinj.core.Address;
import org.bitcoinj.core.AddressFormatException;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.PeerAddress;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.listeners.DownloadProgressTracker;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.kits.WalletAppKit;
import org.bitcoinj.script.Script;
import org.bitcoinj.wallet.SendRequest;
import org.bitcoinj.wallet.Wallet;
import org.bitcoinj.wallet.WalletTransaction;
import org.greenrobot.eventbus.EventBus;

import java.io.File;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class BtcTransactionManager {

    private static final String TAG = "BtcTransactionManager";
    private Context mContext;
    private WalletAppKit kit;
    private NetworkParameters params;
    private static BtcTransactionManager manager;
    private String btcAddress;
    private List<OnSyncListener> listeners;

    public static final BigInteger HIGH_S_DIFFERENCE = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);

    public void addOnSyncListener(OnSyncListener listener) {
        if (listener != null) {
            listeners.add(listener);
        }
    }

    public void removeOnSyncListener(OnSyncListener listener) {
        if (listeners != null && listeners.contains(listener)) {
            listeners.remove(listener);
        }
    }

    public static BtcTransactionManager getInstance(String btcAddress, String filePath) {
        if (manager == null) {
            synchronized (BtcTransactionManager.class) {
                if (manager == null) {
                    manager = new BtcTransactionManager(btcAddress, filePath);
                }
            }
        } else {
            if (!manager.getCurrentBtcAddress().equals(btcAddress)) {
                //更换钱包后，停止同步
                try {
                    manager.stopSync();
                    manager.kit = null;
                } catch (Exception e) {
                    e.printStackTrace();
                }
                //创建新的
                manager = new BtcTransactionManager(btcAddress, filePath);
            }
        }
        return manager;
    }

    private BtcTransactionManager(String btcAddress, String filePath) {
        try {
            this.btcAddress = btcAddress;
            this.mContext = AppContext.getInstance();
            this.params = BtcNetParamUtil.getBtcNetParams();
            this.listeners = new ArrayList<>();
            String filePrefix = filePath.substring(filePath.lastIndexOf("/") + 1, filePath.lastIndexOf("."));
            String pathName = filePath.substring(0, filePath.lastIndexOf("/"));
            kit = new WalletAppKit(params, new File(pathName), filePrefix);
            //设置本地节点
            kit.setPeerNodes(new PeerAddress(params, InetAddress.getByName(EnvironmentVariable
                    .getProperty("btcLocalPeer")), Integer.valueOf(EnvironmentVariable.getProperty("btcLocalPort"))));
            //读取CheckPoints
//            InputStream inputStream = mContext.getAssets().open("checkpoints-testnet.txt");
            InputStream inputStream = mContext.getAssets().open("checkpoints.txt");

            //设置CheckPoints
            kit.setCheckpoints(inputStream);
            //自动保存
            kit.setAutoSave(true);
        } catch (Exception e) {
            e.printStackTrace();

        }
    }

    /**
     * 获取所有地址
     * @return
     */
    public List<String> getChangeAddresses() {
        List<String> ret = new ArrayList<>();
        int issuedKeys = kit.wallet().getActiveKeyChain().getIssuedInternalKeys();

        for (int i = 0; i < issuedKeys; i++) {
            ChildNumber childNumber = new ChildNumber(i, false);
            List<ChildNumber> childNumbers = new ArrayList<>();
            childNumbers.add(ChildNumber.ZERO_HARDENED);
            childNumbers.add(ChildNumber.ONE);
            childNumbers.add(childNumber);
            ECKey key = kit.wallet().getKeyByPath(childNumbers);
            ret.add(new LegacyAddress(params, key.getPubKeyHash()).toBase58());
        }
        return ret;
    }

    /**
     * 获取交易记录
     *
     * @return 交易记录
     */
    public List<TransactionBean> getTransactions() {
        List<TransactionBean> transactionBeanList = new ArrayList<>();
        for (WalletTransaction walletTransaction : kit.wallet().getWalletTransactions()) {
            Transaction tx = walletTransaction.getTransaction();
            //转账金额
            Coin value = tx.getValue(kit.wallet());
            //转账费用
            Coin fee = tx.getFee();
            if (value.toFriendlyString().startsWith("-")) {
                //转出
                value = value.negate().subtract(fee).negate();
            }
            //转账时间
            String time = TimeUtils.millis2String(tx.getUpdateTime().getTime());
            //输入，筛选出目标地址
            List<TransactionInput> inputs = tx.getInputs();
            String fromAddress = "";
            for (int i = 0; i < inputs.size(); i++) {
                if (inputs.get(0).getOutpoint().getConnectedOutput() != null) {
//                    fromAddress = inputs.get(0).getOutpoint().getConnectedOutput()
//                            .getScriptPubKey().getToAddress(params).toString();
                    fromAddress = inputs.get(0).getOutpoint().getConnectedOutput().getScriptPubKey()
                            .getToAddress(params).toString();
                }
            }


//            LogUtils.d("Input-1-->"+btcInputs.get(0).getAddress()+"-->"+btcInputs.get(0).getValue(),
//                    "Input-2-->"+(btcInputs.size() > 1 ? btcInputs.get(1).getAddress():"")+"-->"+(btcInputs.size()>1?btcInputs.get(1).getValue():""),
//                    "Outputs-1-->"+ btcOutputs.get(0).getAddress()+"-->"+btcOutputs.get(0).getValue(),
//                    "Outputs-2-->"+btcOutputs.get(1).getAddress()+"-->"+btcOutputs.get(1).getValue());
            List<TransactionOutput> outputs = tx.getOutputs();
            String toAddress = "";
            TransactionBean transactionBean;
            if (value.toFriendlyString().startsWith("-")) {
                //输出，筛选出目标地址
                for (TransactionOutput output : outputs) {
                    if (output.getValue().equals(value.negate())) {
                        //目标转账地址
                        Script script = output.getScriptPubKey();
                        toAddress = script.getToAddress(params).toString();
                    }
                }
                //转出
                transactionBean = new TransactionBean.Builder().time(time)
                        .gas(tx.getFee() != null ? tx.getFee().toFriendlyString() : "").amount(value.toFriendlyString())
                        .transactionNumber(tx.getHashAsString()).transactionStatus(true)
                        .toAddress(toAddress).fromAddress(manager.getCurrentBtcAddress())
                        .transactionType(TradeLineActivity.TRANSACTION_TYPE_OUT)
                        .build();
//                LogUtils.d("转出", transactionBean);
            } else {
                //输出，筛选出目标地址
                for (TransactionOutput output : outputs) {
                    if (output.getValue().equals(value)) {
                        //目标转账地址
                        Script script = output.getScriptPubKey();
                        toAddress = script.getToAddress(params).toString();
                    }
                }
                transactionBean = new TransactionBean.Builder().time(time)
                        .gas(tx.getFee() != null ? tx.getFee().toFriendlyString() : "").amount(value.toFriendlyString())
                        .transactionNumber(tx.getHashAsString()).transactionStatus(true)
                        .toAddress(toAddress).fromAddress(fromAddress)
                        .transactionType(TradeLineActivity.TRANSACTION_TYPE_IN)
                        .build();
//                LogUtils.d("转入", transactionBean);
            }
            transactionBeanList.add(transactionBean);
        }

        return transactionBeanList;
    }

    public String getCurrentBtcAddress() {
        return btcAddress;
    }

    /**
     * 同步
     */
    public void startSync() {
        // stopSync();
        if (kit.isRunning()) {
            //ToastUtil.showToast(mContext, kit.isRunning() + "");
            updateProgress(100, btcAddress);
            downloadComplete();
            return;
        } else {
            //ToastUtil.showToast(mContext, kit.isRunning() + "");
        }
        try {

            //下载区块数据监听
            kit.setDownloadListener(new DownloadProgressTracker() {
                @Override
                protected void progress(double pct, int blocksSoFar, Date date) {
                    super.progress(pct, blocksSoFar, date);
                    //进度
                    updateProgress(pct, btcAddress);


                }

                @Override
                protected void doneDownload() {
                    super.doneDownload();
                    //完成下载
                    downloadComplete();
                }
            });
            /*
             * If true (the default) then the startup of this service won't be considered complete until the network has been
             * brought up, peer connections established and the block chain synchronised. Therefore {@link #awaitRunning()} can
             * potentially take a very long time. If false, then startup is considered complete once the network activity
             * begins and peer connections/block chain sync will continue in the background.
             */
            kit.setBlockingStartup(false);
            //开始下载
            kit.startAsync();
//            if (kit.peerGroup() != null) {
//                int chainHeight = kit.peerGroup().getMostCommonChainHeight();
//                int localHeight = kit.wallet().getLastBlockSeenHeight();
//                //判断本地的钱包height是不是最新的，是则结束请求
//                if (chainHeight == localHeight) {
//                    //本地已经是最新的，同步结束
//                    Coin balance = kit.wallet().getBalance(Wallet.BalanceType.ESTIMATED_SPENDABLE);
//                    EventBus.getDefault().post(balance);
//                    if (onSyncListener != null) {
//                        onSyncListener.onSyncCompleted();
//                    }
//                }
//            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void downloadComplete() {
        try {
            Coin balance = kit.wallet().getBalance(Wallet.BalanceType.ESTIMATED_SPENDABLE);
            EventBus.getDefault().post(balance);
        } catch (Exception e) {
            e.printStackTrace();
        }
        for (OnSyncListener listener : listeners) {
            if (listener != null) {
                listener.onSyncCompleted((btcAddress));
            }
        }
    }

    //更新进度
    private void updateProgress(double pct, String btcAddress) {
        for (OnSyncListener listener : listeners) {
            if (listener != null) {
                listener.onSyncProgress((int) pct, btcAddress);
//                LogUtils.d("progress:" + pct);
            }
        }
    }


    /**
     * 开始转账
     *
     * @param addr  对方地址
     * @param value Coin 转账金额
     */
    public void startTransaction(String addr, Coin value, Coin feePerKb,
                                 OnTransactionListener onTransactionListener) {
        try {
            // Make sure this code is run in a single thread at once.
            Address address = LegacyAddress.fromBase58(params, addr);
//            System.out.println("address---" + address.toString());
            //构建发送请求
            SendRequest request = SendRequest.to(address, value);
            //找零地址设为付款地址
//            request.setChangeAddress(LegacyAddress.fromBase58(params, btcAddress));
            request.changeAddress = LegacyAddress.fromBase58(params, btcAddress);
            //处理转账费用
            request.feePerKb = feePerKb;
            //发送请求
            Wallet.SendResult result = kit.wallet().sendCoins(kit.peerGroup(), request);
//            System.out.println("coins sent. transaction hash: " + result.tx.getHashAsString());
            LogUtils.d(result.tx.toString());
            if (onTransactionListener != null) {
                onTransactionListener.onTxSuccess(result.tx);
            }
            // you can use a block explorer like https://www.biteasy.com/ to inspect the transaction with the printed transaction hash.
        } catch (InsufficientMoneyException e) {
//            System.out.println("Not enough coins in your wallet. Missing " + e.missing.getValue() + " satoshis are missing (including fees)");
            if (onTransactionListener != null) {
                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_asset_btc_transaction_balance_insufficient));
            }
//            System.out.println("Send money to: " + kit.wallet().currentReceiveAddress().toString());

            // Bitcoinj allows you to define a BalanceFuture to execute a callback once your wallet has a certain balance.
            // Here we wait until the we have enough balance and display a notice.
            // Bitcoinj is using the ListenableFutures of the Guava library. Have a look here for more information: https://github.com/google/guava/wiki/ListenableFutureExplained
//            ListenableFuture<Coin> balanceFuture = kit.wallet().getBalanceFuture(value, Wallet.BalanceType.AVAILABLE);
//            FutureCallback<Coin> callback = new FutureCallback<Coin>() {
//                @Override
//                public void onSuccess(Coin balance) {
//                    System.out.println("coins arrived and the wallet now has enough balance");
//                }
//
//                @Override
//                public void onFailure(Throwable t) {
//                    System.out.println("something went wrong");
//                }
//            };
//            Futures.addCallback(balanceFuture, callback);
        } catch (Wallet.DustySendRequested dustySendRequested) {
            dustySendRequested.printStackTrace();
            if (onTransactionListener != null) {
                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_asset_btc_transaction_balance_enter_low));
            }
        } catch (AddressFormatException e) {
            if (onTransactionListener != null) {
                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_asset_btc_transaction_illegal_collection_address));
            }
        } catch (Exception e) {
            if (onTransactionListener != null) {
                onTransactionListener.onTxFail(ResStringUtil.getString(R.string.open_planet_asset_operation_failed));
            }
        }

        //            InetAddress ip = InetAddress.getByName("206.189.36.36");
//            BlockStore blockStore = new MemoryBlockStore(params);
//            BlockChain chain = new BlockChain(params, wallet, blockStore);
//            PeerGroup peerGroup = new PeerGroup(params, chain);
//            peerGroup.addWallet(wallet);
//            peerGroup.addPeerDiscovery(new DnsDiscovery(params));
//            peerGroup.start();
//            peerGroup.waitForPeers(1);

//            SendRequest request = SendRequest.to(address, Coin.valueOf(10000L));
//            System.out.println("request---" + request.toString());
//// The SendRequest object can be customized at this point to modify how the transaction will be created.
//            wallet.completeTx(request);
//            System.out.println("request-complete---" + request.toString());
//// Ensure these funds won't be spent again.
//            wallet.commitTx(request.tx);
//            System.out.println("request-done---" + request.toString());
//            peerGroup.downloadBlockChain();
//            wallet.saveToFile(...);
// A proposed transaction is now sitting in request.tx - send it in the background.
//            ListenableFuture<Transaction> future = peerGroup.broadcastTransaction(request.tx);

// The future will complete when we've seen the transaction ripple across the network to a sufficient degree.
// Here, we just wait for it to finish, but we can also attach a listener that'll get run on a background
// thread when finished. Or we could just assume the network accepts the transaction and carry on.
//            future.get();

    }

    public interface OnSyncListener {

        void onSyncProgress(int progress, String currentBtcAddress);

        void onSyncCompleted(String currentBtcAddress);

    }

    public interface OnTransactionListener {

        void onTxSuccess(Transaction tx);

        void onTxFail(String e);
    }


    //切换钱包停止同步上一个
    public void stopSync() {
        try {
            if (kit != null && kit.isRunning()) {
                kit.stopAsync();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
